import * as core from 'dva-core';
import { CreateOpts, DvaInstance, utils, CoreOption, Model } from 'dva-core';
import document from 'global/document';
import { createHashHistory, History } from 'history';
import invariant from 'invariant';
import React from 'react';
import { Provider } from 'react-redux';
import { routerMiddleware, routerReducer as routing } from 'react-router-redux';
import { Store } from 'redux';

export interface DvaOption extends Omit<CoreOption, 'history'> {
  history?: History<any>;
}
export default function (opts: DvaOption = {}) {
  const history = opts.history || createHashHistory();
  const createOpts: CreateOpts = {
    initialReducer: {
      routing,
    },
    setupMiddlewares(middlewares) {
      return [routerMiddleware(history), ...middlewares];
    },
    setupApp(app) {
      app._history = patchHistory(history);
    },
  };
  const app = core.create(opts, createOpts);
  const oldAppStart = app.start;
  app.router = router;
  app.start = start;
  return app;

  function router(router: Function) {
    invariant(utils.isFunction(router), `[app.router] router should be function, but got ${typeof router}`);
    app._router = router;
  }

  function start(container?: string | HTMLElement) {
    // 允许 container 是字符串，然后用 querySelector 找元素
    let con: Element | string | null | undefined = container;
    if (typeof con === 'string') {
      con = document.querySelector(con);
      invariant(con, `[app.start] container ${con} not found`);
    }

    // 并且是 HTMLElement
    invariant(!con || isHTMLElement(con), '[app.start] container should be HTMLElement');

    // 路由必须提前注册
    invariant(app._router, '[app.start] router must be registered before app.start()');

    if (!app._store) {
      oldAppStart.call(app);
    }
    const store = app._store;

    // export _getProvider for HMR
    // ref: https://github.com/dvajs/dva/issues/469
    app._getProvider = getProvider.bind(null, store, app);

    // If has container, render; else, return react component
    if (con) {
      render(con, store, app, app._router);
      app._plugin.apply('onHmr')(render.bind(null, con, store, app));
    } else {
      return getProvider(store, this, this._router);
    }
  }
}

function isHTMLElement(node: any): node is HTMLElement {
  return typeof node === 'object' && node !== null && node.nodeType && node.nodeName;
}
function getProvider(store: core.Store, app: DvaInstance, router: Function) {
  const DvaRoot = (extraProps?: object) => (
    <Provider store={store as Store}>{router({ app, history: app._history, ...extraProps })}</Provider>
  );
  return DvaRoot;
}

function render(container: HTMLElement, store: core.Store, app: DvaInstance, router: Function) {
  const ReactDOM = require('react-dom'); // eslint-disable-line
  ReactDOM.render(React.createElement(getProvider(store, app, router)), container);
}

function patchHistory(history: History) {
  const oldListen = history.listen;
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
  // @ts-ignore
  history.listen = (callback: (location: History['location']) => void) => {
    callback(history.location);
    return oldListen.call(history, callback);
  };
  return history;
}
